/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package template

import (
	"bytes"
	"k8s.io/klog/v2"
	"tmlake.com/api-gateway/istio-agent/proxy/njetcontroller/configs"
	"tmlake.com/api-gateway/pkg/security"

	"encoding/json"
	"fmt"
	"io"
	"math/rand" // #nosec
	"net"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"
	text_template "text/template"
	"time"
	njt_log "tmlake.com/api-gateway/istio-agent/proxy"
	"tmlake.com/api-gateway/istio-agent/proxy/njetcontroller/controller/config"
)

const (
	slash                   = "/"
	nonIdempotent           = "non_idempotent"
	defBufferSize           = 65535
	writeIndentOnEmptyLines = true // backward-compatibility
	njet502Server           = "unix:/var/lib/njet/njet-502-server.sock"
	njet404Server           = "unix:/var/lib/njet/njet-404-server.sock"
)

const (
	stateCode = iota
	stateComment
)

// Writer is the interface to render a template
type Writer interface {
	// Write renders the template.
	// NOTE: Implementors must ensure that the content of the returned slice is not modified by the implementation
	// after the return of this function.
	Write(conf config.TemplateConfig) ([]byte, error)
}

// Template ...
type Template struct {
	tmpl *text_template.Template
	//fw   watch.FileWatcher
	bp *BufferPool
}

//NewTemplate returns a new Template instance or an
//error if the specified template file contains errors
func NewTemplate(file string) (*Template, error) {
	data, err := os.ReadFile(file)
	if err != nil {
		return nil, fmt.Errorf("unexpected error reading template %s: %w", file, err)
	}

	tmpl, err := text_template.New("njet.tmpl").Funcs(funcMap).Parse(string(data))
	if err != nil {
		return nil, err
	}

	return &Template{
		tmpl: tmpl,
		bp:   NewBufferPool(defBufferSize),
	}, nil
}

// 1. Removes carriage return symbol (\r)
// 2. Collapses multiple empty lines to single one
// 3. Re-indent
// (ATW: always returns nil)
func cleanConf(in *bytes.Buffer, out *bytes.Buffer) error {
	depth := 0
	lineStarted := false
	emptyLineWritten := false
	state := stateCode
	for {
		c, err := in.ReadByte()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err // unreachable
		}

		needOutput := false
		nextDepth := depth
		nextLineStarted := lineStarted

		switch state {
		case stateCode:
			switch c {
			case '{':
				needOutput = true
				nextDepth = depth + 1
				nextLineStarted = true
			case '}':
				needOutput = true
				depth--
				nextDepth = depth
				nextLineStarted = true
			case ' ', '\t':
				needOutput = lineStarted
			case '\r':
			case '\n':
				needOutput = !(!lineStarted && emptyLineWritten)
				nextLineStarted = false
			case '#':
				needOutput = true
				nextLineStarted = true
				state = stateComment
			default:
				needOutput = true
				nextLineStarted = true
			}
		case stateComment:
			switch c {
			case '\r':
			case '\n':
				needOutput = true
				nextLineStarted = false
				state = stateCode
			default:
				needOutput = true
			}
		}

		if needOutput {
			if !lineStarted && (writeIndentOnEmptyLines || c != '\n') {
				for i := 0; i < depth; i++ {
					err = out.WriteByte('\t') // always nil
					if err != nil {
						return err
					}
				}
			}
			emptyLineWritten = !lineStarted
			err = out.WriteByte(c) // always nil
			if err != nil {
				return err
			}
		}

		depth = nextDepth
		lineStarted = nextLineStarted
	}
}

// Write populates a buffer using a template with NJET configuration
// and the servers and upstreams created by xds
func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
	tmplBuf := t.bp.Get()
	defer t.bp.Put(tmplBuf)

	outCmdBuf := t.bp.Get()
	defer t.bp.Put(outCmdBuf)

	if klog.V(3).Enabled() {
		b, err := json.Marshal(conf)
		if err != nil {
			klog.Errorf("unexpected error: %v", err)
		}
		klog.InfoS("NJET", "configuration", string(b))
	}

	err := t.tmpl.Execute(tmplBuf, conf)
	if err != nil {
		return nil, err
	}

	// squeezes multiple adjacent empty lines to be single
	// spaced this is to avoid the use of regular expressions
	err = cleanConf(tmplBuf, outCmdBuf)
	if err != nil {
		return nil, err
	}

	// make a copy to ensure that we are no longer modifying the content of the buffer
	out := outCmdBuf.Bytes()
	res := make([]byte, len(out))
	copy(res, out)

	return res, nil
}

var (
	funcMap = text_template.FuncMap{
		"empty": func(input interface{}) bool {
			check, ok := input.(string)
			if ok {
				return len(check) == 0
			}
			return true
		},
		"buildLuaSharedDictionaries":      buildLuaSharedDictionaries,
		"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
		"buildLocation":                   buildLocation,
		"buildProxyPass":                  buildProxyPass,
		"configForLua":                    configForLua,
		"buildUpstreamName":               buildUpstreamName,
		"buildUpstream":                   buildUpstream,
		"getenv":                          os.Getenv,
		"contains":                        strings.Contains,
		"split":                           strings.Split,
		"hasPrefix":                       strings.HasPrefix,
		"hasSuffix":                       strings.HasSuffix,
		"trimSpace":                       strings.TrimSpace,
		"toUpper":                         strings.ToUpper,
		"toLower":                         strings.ToLower,
		"formatIP":                        formatIP,
		"quote":                           quote,
		"buildNextUpstream":               buildNextUpstream,
		"serverConfig": func(all config.TemplateConfig, server *configs.VirtualServerConfig) interface{} {
			return struct{ First, Second interface{} }{all, server}
		},
		"buildForwardedFor":    buildForwardedFor,
		"proxySetHeader":       proxySetHeader,
		"enforceRegexModifier": enforceRegexModifier,
		"buildCustomErrorDeps": buildCustomErrorDeps,
		"buildHTTPListener":    buildHTTPListener,
		"buildHTTPSListener":   buildHTTPSListener,
		"buildServerName":      buildServerName,
		"buildCorsOriginRegex": buildCorsOriginRegex,
		"buildSSL":             buildSSL,
		"buildProxySSL":        buildProxySSL,
		"buildPortSSL":         buildPortSSL,
		"buildProxySSLConf":    buildProxySSLConf,
	}
)

// formatIP will wrap IPv6 addresses in [] and return IPv4 addresses
// without modification. If the input cannot be parsed as an IP address
// it is returned without modification.
func formatIP(input string) string {
	ip := net.ParseIP(input)
	if ip == nil {
		return input
	}
	if v4 := ip.To4(); v4 != nil {
		return input
	}
	return fmt.Sprintf("[%s]", input)
}

func quote(input interface{}) string {
	var inputStr string
	switch input := input.(type) {
	case string:
		inputStr = input
	case fmt.Stringer:
		inputStr = input.String()
	case *string:
		inputStr = *input
	default:
		inputStr = fmt.Sprintf("%v", input)
	}
	return fmt.Sprintf("%q", inputStr)
}

func buildLuaSharedDictionaries(c interface{}, s interface{}) string {
	var out []string

	cfg, ok := c.(config.Configuration)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a 'config.Configuration' type but %T was returned", c)
		return ""
	}

	_, ok = s.([]*configs.VirtualServerConfig)
	if !ok {
		njt_log.NjetLogger.Errorf("expected an '[]*configs.Server' type but %T was returned", s)
		return ""
	}

	for name, size := range cfg.LuaSharedDicts {
		sizeStr := dictKbToStr(size)
		out = append(out, fmt.Sprintf("lua_shared_dict %s %s", name, sizeStr))
	}

	sort.Strings(out)

	return strings.Join(out, ";\n") + ";\n"
}

func luaConfigurationRequestBodySize(c interface{}) string {
	cfg, ok := c.(config.Configuration)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a 'config.Configuration' type but %T was returned", c)
		return "100M" // just a default number
	}

	size := cfg.LuaSharedDicts["configuration_data"]
	if size < cfg.LuaSharedDicts["certificate_data"] {
		size = cfg.LuaSharedDicts["certificate_data"]
	}
	size = size + 1024

	return dictKbToStr(size)
}

// configForLua returns some general configuration as Lua table represented as string
func configForLua(input interface{}) string {
	all, ok := input.(config.TemplateConfig)
	if !ok {
		klog.Errorf("expected a 'config.TemplateConfig' type but %T was given", input)
		return "{}"
	}

	return fmt.Sprintf(`{
		use_forwarded_headers = %t,
		use_proxy_protocol = %t,
		is_ssl_passthrough_enabled = %t,
		http_redirect_code = %v,
		listen_ports = { ssl_proxy = "%v", https = "%v" },

		hsts = %t,
		hsts_max_age = %v,
		hsts_include_subdomains = %t,
		hsts_preload = %t,

		global_throttle = {
			memcached = {
				host = "%v", port = %d, connect_timeout = %d, max_idle_timeout = %d, pool_size = %d,
			},
			status_code = %d,
		}
	}`,
		all.Cfg.UseForwardedHeaders,
		all.Cfg.UseProxyProtocol,
		all.IsSSLPassthroughEnabled,
		all.Cfg.HTTPRedirectCode,
		all.ListenPorts.SSLProxy,
		all.ListenPorts.HTTPS,

		all.Cfg.HSTS,
		all.Cfg.HSTSMaxAge,
		all.Cfg.HSTSIncludeSubdomains,
		all.Cfg.HSTSPreload,

		all.Cfg.GlobalRateLimitMemcachedHost,
		all.Cfg.GlobalRateLimitMemcachedPort,
		all.Cfg.GlobalRateLimitMemcachedConnectTimeout,
		all.Cfg.GlobalRateLimitMemcachedMaxIdleTimeout,
		all.Cfg.GlobalRateLimitMemcachedPoolSize,
		all.Cfg.GlobalRateLimitStatucCode,
	)
}

func enforceRegexModifier(input interface{}) bool {
	return false
}

// buildLocation produces the location string
func buildLocation(input interface{}, enforceRegex bool) string {
	location, ok := input.(*configs.Location)
	if !ok {
		njt_log.NjetLogger.Errorf("expected an '*configs.Location' type but %T was returned", input)
		return slash
	}

	path := location.Path
	if location.Internal {
		return fmt.Sprintf("= %s", path)
	}
	if enforceRegex {
		return fmt.Sprintf(`~* "^%s"`, path)
	}

	return path
}

func buildProxySSLConfByBackend(backend *configs.Backend) string {
	SSLCertificate := "proxy_ssl_certificate"
	SSLCertificateKey := "proxy_ssl_certificate_key"
	SSLTrustedCertificate := "proxy_ssl_trusted_certificate"
	SSLVerify := "proxy_ssl_verify"
	SSLVerifyDepth := "proxy_ssl_verify_depth"
	SSLName := "proxy_ssl_name"

	var sslDirectives []string
	switch backend.SSLMode {
	case configs.Client_ISTIO_MUTUAL, configs.Client_MUTUAL:
		if backend.SSLCert.SSLCertificate == security.WorkloadKeyCertResourceName {
			certificate := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "cert-chain.pem"}, "/")
			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificate, certificate))
			certificateKey := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "key.pem"}, "/")
			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificateKey, certificateKey))

		}

		sslDirectives = append(sslDirectives, fmt.Sprintf("%s on", SSLVerify))
		if backend.ProxySSL.CACertificate == security.RootCertReqResourceName {
			root := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "root-cert.pem"}, "/")
			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLTrustedCertificate, root))
		}
		sslDirectives = append(sslDirectives, fmt.Sprintf("%s %d", SSLVerifyDepth, backend.ProxySSL.VerifyDepth))
		if backend.ProxySSL.ProxySSLName != "" {
			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %q", SSLName, backend.ProxySSL.ProxySSLName))
		}

	}

	if len(sslDirectives) == 0 {
		return ""
	} else {
		return strings.Join(sslDirectives, ";\n") + ";"
	}
}
func buildProxySSLConf(loc interface{}, backends interface{}) string {
	location, ok := loc.(*configs.Location)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '*configs.Location' type but %T was returned", loc)
		return ""
	}

	bs, ok := backends.([]*configs.Backend)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '[]*configs.Backend' type but %T was returned", bs)
		return ""
	}

	for _, backend := range bs {
		if backend.Name == location.UpstreamName {
			if backend.SSLMode == configs.Client_ISTIO_MUTUAL || backend.SSLMode == configs.Client_MUTUAL {
				return buildProxySSLConfByBackend(backend)
			}
			break
		}
	}

	return ""
}

func generateProxyPassProtocol(enableTLS bool) string {
	if enableTLS {
		return "https"
	}
	return "http"
}

func generateProxyPass(tlsEnabled bool, upstreamName string, internal bool) string {
	proxyPass := fmt.Sprintf("%v://%v", generateProxyPassProtocol(tlsEnabled), upstreamName)

	if internal {
		return fmt.Sprintf("%v$request_uri", proxyPass)
	}

	return proxyPass
}

func buildProxyPass(loc interface{}, backends interface{}) string {
	location, ok := loc.(*configs.Location)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '*configs.Location' type but %T was returned", loc)
		return ""
	}

	bs, ok := backends.([]*configs.Backend)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '[]*configs.Backend' type but %T was returned", bs)
		return ""
	}

	proxyPass := "proxy_pass"
	tlsEnabled := false

	for _, backend := range bs {
		if backend.Name == location.UpstreamName {
			switch backend.SSLMode {
			case configs.Client_ISTIO_MUTUAL, configs.Client_MUTUAL:
				tlsEnabled = true
			}
			break
		}
	}

	if location.UpstreamName == "upstream-default-backend" {
		tlsEnabled = false
	}
	// defProxyPass returns the default proxy_pass, just the name of the upstream
	defProxyPass := fmt.Sprintf("%v %s;", proxyPass, generateProxyPass(tlsEnabled, location.UpstreamName, location.Internal))

	return defProxyPass
}

var (
	denyPathSlugMap = map[string]string{}
)

func buildUpstreamName(loc interface{}) string {
	location, ok := loc.(*configs.Location)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '*configs.Location' type but %T was returned", loc)
		return ""
	}

	return location.UpstreamName
}

func buildUpstream(p interface{}) string {
	endpoint, ok := p.(configs.Endpoint)
	if !ok {
		njt_log.NjetLogger.Errorf("expected an 'configs.Endpoint' type but %T was returned", p)
		return ""
	}
	var server string
	if endpoint.Address == njet502Server {
		server = njet502Server
	} else if endpoint.Address == njet404Server {
		server = njet404Server
	} else {
		server = net.JoinHostPort(endpoint.Address, endpoint.Port)
	}

	return server
}

func buildNextUpstream(i, r interface{}) string {
	nextUpstream, ok := i.(string)
	if !ok {
		klog.Errorf("expected a 'string' type but %T was returned", i)
		return ""
	}

	retryNonIdempotent := r.(bool)

	parts := strings.Split(nextUpstream, " ")

	nextUpstreamCodes := make([]string, 0, len(parts))
	for _, v := range parts {
		if v != "" && v != nonIdempotent {
			nextUpstreamCodes = append(nextUpstreamCodes, v)
		}

		if v == nonIdempotent {
			retryNonIdempotent = true
		}
	}

	if retryNonIdempotent {
		nextUpstreamCodes = append(nextUpstreamCodes, nonIdempotent)
	}

	return strings.Join(nextUpstreamCodes, " ")
}

// refer to http://njet.org/en/docs/syntax.html
// Nginx differentiates between size and offset
// offset directives support gigabytes in addition
var njetSizeRegex = regexp.MustCompile("^[0-9]+[kKmM]{0,1}$")
var njetOffsetRegex = regexp.MustCompile("^[0-9]+[kKmMgG]{0,1}$")

type configsInformation struct {
	Namespace   string
	Path        string
	Rule        string
	Service     string
	ServicePort string
	Annotations map[string]string
}

func (info *configsInformation) Equal(other *configsInformation) bool {
	if info.Namespace != other.Namespace {
		return false
	}
	if info.Rule != other.Rule {
		return false
	}
	if info.Service != other.Service {
		return false
	}
	if info.ServicePort != other.ServicePort {
		return false
	}
	if !reflect.DeepEqual(info.Annotations, other.Annotations) {
		return false
	}

	return true
}

func buildForwardedFor(input interface{}) string {
	s, ok := input.(string)
	if !ok {
		klog.Errorf("expected a 'string' type but %T was returned", input)
		return ""
	}

	ffh := strings.Replace(s, "-", "_", -1)
	ffh = strings.ToLower(ffh)
	return fmt.Sprintf("$http_%v", ffh)
}

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func init() {
	rand.Seed(time.Now().UnixNano())
}

func randomString() string {
	b := make([]rune, 32)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))] // #nosec
	}

	return string(b)
}

func proxySetHeader(loc interface{}) string {
	location, ok := loc.(*configs.Location)
	if !ok {
		klog.Errorf("expected a '*configs.Location' type but %T was returned", loc)
		return "proxy_set_header"
	}

	if location.BackendProtocol == "GRPC" || location.BackendProtocol == "GRPCS" {
		return "grpc_set_header"
	}

	return "proxy_set_header"
}

// buildCustomErrorDeps is a utility function returning a struct wrapper with
// the data required to build the 'CUSTOM_ERRORS' template
func buildCustomErrorDeps(upstreamName string, errorCodes []int, enableMetrics bool, modsecurityEnabled bool) interface{} {
	return struct {
		UpstreamName       string
		ErrorCodes         []int
		EnableMetrics      bool
		ModsecurityEnabled bool
	}{
		UpstreamName:       upstreamName,
		ErrorCodes:         errorCodes,
		EnableMetrics:      enableMetrics,
		ModsecurityEnabled: modsecurityEnabled,
	}
}

func buildHTTPListener(t interface{}, s interface{}) string {
	var out []string

	tc, ok := t.(config.TemplateConfig)
	if !ok {
		klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
		return ""
	}

	hostname, ok := s.(string)
	if !ok {
		klog.Errorf("expected a 'string' type but %T was returned", s)
		return ""
	}

	addrV4 := []string{""}
	if len(tc.Cfg.BindAddressIpv4) > 0 {
		addrV4 = tc.Cfg.BindAddressIpv4
	}

	co := commonListenOptions(tc, hostname)

	out = append(out, httpListener(addrV4, co, tc)...)

	if !tc.IsIPV6Enabled {
		return strings.Join(out, "\n")
	}

	addrV6 := []string{"[::]"}
	if len(tc.Cfg.BindAddressIpv6) > 0 {
		addrV6 = tc.Cfg.BindAddressIpv6
	}

	out = append(out, httpListener(addrV6, co, tc)...)

	return strings.Join(out, "\n")
}

func buildHTTPSListener(t interface{}, s interface{}) string {
	var out []string

	tc, ok := t.(config.TemplateConfig)
	if !ok {
		klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
		return ""
	}

	hostname, ok := s.(string)
	if !ok {
		klog.Errorf("expected a 'string' type but %T was returned", s)
		return ""
	}

	co := commonListenOptions(tc, hostname)

	addrV4 := []string{""}
	if len(tc.Cfg.BindAddressIpv4) > 0 {
		addrV4 = tc.Cfg.BindAddressIpv4
	}

	out = append(out, httpsListener(addrV4, co, tc)...)

	if !tc.IsIPV6Enabled {
		return strings.Join(out, "\n")
	}

	addrV6 := []string{"[::]"}
	if len(tc.Cfg.BindAddressIpv6) > 0 {
		addrV6 = tc.Cfg.BindAddressIpv6
	}

	out = append(out, httpsListener(addrV6, co, tc)...)

	return strings.Join(out, "\n")
}

func commonListenOptions(template config.TemplateConfig, hostname string) string {
	var out []string

	if template.Cfg.UseProxyProtocol {
		out = append(out, "proxy_protocol")
	}

	if hostname != "_" {
		return strings.Join(out, " ")
	}

	// setup options that are valid only once per port

	out = append(out, "default_server")

	if template.Cfg.ReusePort {
		out = append(out, "reuseport")
	}

	out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize))

	return strings.Join(out, " ")
}

func httpListener(addresses []string, co string, tc config.TemplateConfig) []string {
	out := make([]string, 0)
	for _, address := range addresses {
		lo := []string{"listen"}

		if address == "" {
			lo = append(lo, fmt.Sprintf("%v", tc.ListenPorts.HTTPProxy))
		} else {
			lo = append(lo, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTPProxy))
		}

		lo = append(lo, co)
		lo = append(lo, ";")
		out = append(out, strings.Join(lo, " "))
	}

	return out
}

func httpsListener(addresses []string, co string, tc config.TemplateConfig) []string {
	out := make([]string, 0)
	for _, address := range addresses {
		lo := []string{"listen"}

		if tc.IsSSLPassthroughEnabled {
			if address == "" {
				lo = append(lo, fmt.Sprintf("%v", tc.ListenPorts.SSLProxy))
			} else {
				lo = append(lo, fmt.Sprintf("%v:%v", address, tc.ListenPorts.SSLProxy))
			}

			if !strings.Contains(co, "proxy_protocol") {
				lo = append(lo, "proxy_protocol")
			}
		} else {
			if address == "" {
				lo = append(lo, fmt.Sprintf("%v", tc.ListenPorts.HTTPS))
			} else {
				lo = append(lo, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTPS))
			}
		}

		lo = append(lo, co)
		lo = append(lo, "ssl")

		if tc.Cfg.UseHTTP2 {
			lo = append(lo, "http2")
		}

		lo = append(lo, ";")
		out = append(out, strings.Join(lo, " "))
	}

	return out
}

// buildServerName ensures wildcard hostnames are valid
func buildServerName(hostname string) string {
	if !strings.HasPrefix(hostname, "*") {
		return hostname
	}

	hostname = strings.Replace(hostname, "*.", "", 1)
	parts := strings.Split(hostname, ".")

	return `~^(?<subdomain>[\w-]+)\.` + strings.Join(parts, "\\.") + `$`
}

func convertGoSliceIntoLuaTable(goSliceInterface interface{}, emptyStringAsNil bool) (string, error) {
	goSlice := reflect.ValueOf(goSliceInterface)
	kind := goSlice.Kind()

	switch kind {
	case reflect.String:
		if emptyStringAsNil && len(goSlice.Interface().(string)) == 0 {
			return "nil", nil
		}
		return fmt.Sprintf(`"%v"`, goSlice.Interface()), nil
	case reflect.Int, reflect.Bool:
		return fmt.Sprintf(`%v`, goSlice.Interface()), nil
	case reflect.Slice, reflect.Array:
		luaTable := "{ "
		for i := 0; i < goSlice.Len(); i++ {
			luaEl, err := convertGoSliceIntoLuaTable(goSlice.Index(i).Interface(), emptyStringAsNil)
			if err != nil {
				return "", err
			}
			luaTable = luaTable + luaEl + ", "
		}
		luaTable += "}"
		return luaTable, nil
	default:
		return "", fmt.Errorf("could not process type: %s", kind)
	}
}

func buildOriginRegex(origin string) string {
	origin = regexp.QuoteMeta(origin)
	origin = strings.Replace(origin, "\\*", `[A-Za-z0-9\-]+`, 1)
	return fmt.Sprintf("(%s)", origin)
}

func buildCorsOriginRegex(corsOrigins []string) string {
	if len(corsOrigins) == 1 && corsOrigins[0] == "*" {
		return "set $http_origin *;\nset $cors 'true';"
	}

	var originsRegex string = "if ($http_origin ~* ("
	for i, origin := range corsOrigins {
		originTrimmed := strings.TrimSpace(origin)
		if len(originTrimmed) > 0 {
			builtOrigin := buildOriginRegex(originTrimmed)
			originsRegex += builtOrigin
			if i != len(corsOrigins)-1 {
				originsRegex = originsRegex + "|"
			}
		}
	}
	originsRegex = originsRegex + ")$ ) { set $cors 'true'; }"
	return originsRegex
}

func buildSSL(service *configs.L4Service) string {
	if service == nil {
		njt_log.NjetLogger.Errorf("L4 service is nil")
	}

	SSLCertificate := "ssl_certificate"
	SSLCertificateKey := "ssl_certificate_key"
	SSLClientCertificate := "ssl_client_certificate"
	SSLVerifyClient := "ssl_verify_client"
	SSLVerifyDepth := "ssl_verify_depth"
	var sslDirectives []string

	//var hasTLSMode bool
	//for destinationPort, mode := range service.DestinationPortPeerAuthenticationMode {
	//	if destinationPort == "0" {
	//		continue
	//	}
	//	switch mode {
	//	case ingress.PERMISSIVE, ingress.STRICT:
	//		//case ingress.STRICT:
	//		if service.SSLCert[destinationPort].SSLCertificate == security.WorkloadKeyCertResourceName {
	//			certificate := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "cert-chain.pem"}, "/")
	//			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificate, certificate))
	//			certificateKey := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "key.pem"}, "/")
	//			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificateKey, certificateKey))
	//
	//		}
	//
	//		if service.CertificateAuth[destinationPort].VerifyClient {
	//			sslDirectives = append(sslDirectives, fmt.Sprintf("%s on", SSLVerifyClient))
	//			if service.CertificateAuth[destinationPort].CACertificate == security.RootCertReqResourceName {
	//				root := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "root-cert.pem"}, "/")
	//				sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLClientCertificate, root))
	//			}
	//			sslDirectives = append(sslDirectives, fmt.Sprintf("%s %d", SSLVerifyDepth, service.CertificateAuth[destinationPort].ValidationDepth))
	//		}
	//		hasTLSMode = true
	//	}
	//	if hasTLSMode {
	//		break
	//	}
	//}

	//When the security policy is ingress.DISABLE, SSL is configured because of a bug in the sidecar
	certificate := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "cert-chain.pem"}, "/")
	sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificate, certificate))
	certificateKey := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "key.pem"}, "/")
	sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificateKey, certificateKey))

	sslDirectives = append(sslDirectives, fmt.Sprintf("%s on", SSLVerifyClient))
	root := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "root-cert.pem"}, "/")
	sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLClientCertificate, root))

	sslDirectives = append(sslDirectives, fmt.Sprintf("%s %d", SSLVerifyDepth, 10))

	if len(sslDirectives) >= 1 {
		return strings.Join(sslDirectives, ";\n") + ";"
	} else {
		return ""
	}
}

func buildPortSSL(port string, service *configs.L4Service) string {
	if service == nil {
		njt_log.NjetLogger.Errorf("L4 service is nil")
	}

	SSLCertificate := "#ssl_certificate"
	SSLCertificateKey := "#ssl_certificate_key"
	SSLClientCertificate := "#ssl_client_certificate"
	SSLVerifyClient := "#ssl_verify_client"
	SSLVerifyDepth := "#ssl_verify_depth"
	var sslDirectives []string
	for destinationPort, mode := range service.DestinationPortPeerAuthenticationMode {
		if destinationPort == port {
			switch mode {
			case configs.PERMISSIVE, configs.STRICT:
				//case configs.STRICT:
				if service.SSLCert[destinationPort].SSLCertificate == security.WorkloadKeyCertResourceName {
					certificate := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "cert-chain.pem"}, "/")
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificate, certificate))
					certificateKey := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "key.pem"}, "/")
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificateKey, certificateKey))

				}

				if service.CertificateAuth[destinationPort].VerifyClient {
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s on", SSLVerifyClient))
					if service.CertificateAuth[destinationPort].CACertificate == security.RootCertReqResourceName {
						root := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "root-cert.pem"}, "/")
						sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLClientCertificate, root))
					}
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %d", SSLVerifyDepth, service.CertificateAuth[destinationPort].ValidationDepth))
				}

			}
		}

	}

	return strings.Join(sslDirectives, ";\n")
}

func buildProxySSL(backName interface{}, backends interface{}) string {
	name, ok := backName.(string)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a 'string' type but %T was returned", name)
		return ""
	}

	bs, ok := backends.([]*configs.Backend)
	if !ok {
		njt_log.NjetLogger.Errorf("expected a '[]*configs.Backend' type but %T was returned", bs)
		return ""
	}

	SSLCertificate := "#proxy_ssl_certificate"
	SSLCertificateKey := "#proxy_ssl_certificate_key"
	SSLTrustedCertificate := "#proxy_ssl_trusted_certificate"
	SSLVerify := "#proxy_ssl_verify"
	SSLVerifyDepth := "#proxy_ssl_verify_depth"
	SSLName := "#proxy_ssl_name"

	var sslDirectives []string
	for _, backend := range bs {
		if backend.Name == backName {
			switch backend.SSLMode {
			case configs.Client_MUTUAL:
			case configs.Client_ISTIO_MUTUAL:
				if backend.SSLCert.SSLCertificate == security.WorkloadKeyCertResourceName {
					certificate := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "cert-chain.pem"}, "/")
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificate, certificate))
					certificateKey := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "key.pem"}, "/")
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLCertificateKey, certificateKey))

				}

				sslDirectives = append(sslDirectives, fmt.Sprintf("%s on", SSLVerify))
				if backend.ProxySSL.CACertificate == security.RootCertReqResourceName {
					root := strings.Join([]string{os.Getenv("OUTPUT_CERTS"), "root-cert.pem"}, "/")
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %s", SSLTrustedCertificate, root))
				}
				sslDirectives = append(sslDirectives, fmt.Sprintf("%s %d", SSLVerifyDepth, backend.ProxySSL.VerifyDepth))
				if backend.ProxySSL.ProxySSLName != "" {
					sslDirectives = append(sslDirectives, fmt.Sprintf("%s %q", SSLName, backend.ProxySSL.ProxySSLName))
				}

			}

			break
		}
	}

	if len(sslDirectives) == 0 {
		return ""
	}

	return strings.Join(sslDirectives, ";\n") + ";"
}
